/**
 * 1. Promise 类，实例化时需要传入一个执行器
 * 2. promise 三种状态，一旦确定，不可更改
 * 3. resolve & reject 函数用来更改状态
 * 4. then 方法判断当前状态，执行此状态的回调,then 可以链式调用，下一个then的回调函数拿到的值是上一个的返回值
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }

  static all(array) {
    const result = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function add(i, value) {
        result[i] = value
        index++
        if (index === array.length) resolve(result)
      }
      for (let i = 0; i < array.length; i++) {
        const current = array[i]
        if (current instanceof MyPromise) {
          current.then((value) => {
            add(i, value)
          }, reject)
        } else {
          add(i, current)
        }
      }
    })
  }

  static race(array) {
    let flag = false
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < array.length; i++) {
        if (flag) return
        const current = array[i]
        if (current instanceof MyPromise) {
          current.then((value) => {
            if (!flag) {
              flag = true
              resolve(value)
            }
          }, (error) => {
            if (!flag) {
              flag = true
              reject(error)
            }
          })
        } else {
          if (!flag) {
            flag = true
            resolve(current)
          }
        }
      }
    })
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise((resolve) => {
      resolve(value)
    })
  }

  static reject(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise((resolve, reject) => {
      reject(value)
    })
  }

  status = PENDING
  result = undefined
  resolveCallback = []
  error = undefined
  rejectCallback = []

  resolve = (result) => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.result = result
    while (this.resolveCallback.length > 0) this.resolveCallback.shift()()
  }

  reject = (error) => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.error = error
    while (this.rejectCallback.length > 0) this.rejectCallback.shift()()
  }

  then(resolveCallback, rejectCallback) {
    // then 不传参数补默认方法
    resolveCallback = resolveCallback ? resolveCallback : (v => v)
    rejectCallback = rejectCallback ? rejectCallback : (v => v)
    /**
     * 判断返回值
     * 如果是 promise ，返回 promise，如果是自己本身循环调用，则 reject() 抛出异常
     * 如果其他类型，返回 resolve(x)
     * 
     * @param {*} promise 
     * @param {*} x 
     * @param {*} resolve 
     * @param {*} reject 
     */
    function resolvePromise(promise, x, resolve, reject) {
      if (promise === x) { // 防止循环调用自己
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
      }
      if (x instanceof MyPromise) {
        x.then(resolve, reject)
      } else {
        resolve(x)
      }
    }
    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            resolvePromise(promise, resolveCallback(this.result), resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            resolvePromise(promise, rejectCallback(this.error), resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else { // Pending
        // 需要存储成功回调和失败回调，等待状态改变时调用
        this.resolveCallback.push(() => {
          setTimeout(() => {
            try {
              resolvePromise(promise, resolveCallback(this.result), resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        this.rejectCallback.push(() => {
          setTimeout(() => {
            try {
              resolvePromise(promise, rejectCallback(this.error), resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise
  }

  catch(rejectCallback) {
    return this.then(undefined, rejectCallback)
  }

  finally(callback) {
    return this.then((value) => {
      return MyPromise.resolve(callback()).then(() => value)
    }, (error) => {
      return MyPromise.resolve(callback()).then(() => {
        throw error
      })
    })
  }
}

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    // resolve('success')
    reject('error')
  }, 2000)
  // reject('error')
}).catch((error) => {
  console.log('catch', error)
})

const p2 = promise.then((value) => {
  console.log(1, value)
  throw new Error('then error')
})

p2.then(() => {

})

promise.then((value) => {
  console.log(2, value)
}, (reason) => {
  console.log(reason)
})

promise.then((value) => {
  console.log(3, value)
}, (reason) => {
  console.log(reason)
})
 //////////////////
// const promise2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve('success')
//   }, 2000)
//   // reject('error')
// }).catch((error) => {
//   console.log('catch', error)
// })

// const p22 = promise2.then((value) => {
//   console.log(1, value)
//   throw new Error('then error')
// })

// p22.then(() => {

// })

// promise2.then((value) => {
//   console.log(2, value)
// }, (reason) => {
//   console.log(reason)
// })

// promise2.then((value) => {
//   console.log(3, value)
// }, (reason) => {
//   console.log(reason)
// })
////////////////////
// let p1 = new MyPromise((resolve) => {
//   setTimeout(() => {
//     resolve(3)
//   }, 1000)
// })

// MyPromise.all([1, 2, p1, 4])
//   .then((arr) => {
//     console.log('all', arr)
//   })

// MyPromise.race([1, 2, p1, 4])
//   .then((arr) => {
//     console.log('race', arr)
//   })

